﻿use std::collections::BTreeMap;

use sqlx::PgPool;
use wiremock::MockServer;
use zero2prod::{
    email_client::EmailClient,
    issue_delivery_worker::{try_execute_task, ExecutionOutcome},
};

use super::TestUser;

/// reqwest 传递 Cookie 的条件：
/// 1. reqwest::ClientBuilder::cookie_store(true)
/// 2. 所有请求使用相同的 reqwest::Client 实例
pub struct TestApp {
    pub port: u16,
    pub address: String,
    pub db_pool: PgPool,
    pub email_server: MockServer,
    pub test_user: TestUser,
    pub api_client: reqwest::Client,
    pub email_client: EmailClient,
}

/// 在发送给邮件 API 的请求中所包含的确认链接
pub struct ConfirmationLinks {
    pub html: reqwest::Url,
    pub text: reqwest::Url,
}

impl TestApp {
    pub async fn dispatch_all_pending_emails(&self) {
        loop {
            if let ExecutionOutcome::EmptyQueue =
                try_execute_task(&self.db_pool, &self.email_client)
                    .await
                    .unwrap()
            {
                break;
            }
        }
    }
    pub async fn post_subscription(&self, body: String) -> reqwest::Response {
        self.api_client
            .post(format!("{}/subscriptions", self.address))
            .header("Content-Type", "application/x-www-form-urlencoded")
            .body(body)
            .send()
            .await
            .expect("Failed to execute request.")
    }

    pub fn get_configuration_links(&self, email_request: &wiremock::Request) -> ConfirmationLinks {
        // 将正文从字节流转换为 BTreeMap
        // 正文内容示例："from=johnsmithcry%40163.com&to=ursula_le_guin%40gmail.com&subject=Welcome%21&html=Welcome+to+our+newsletter%21%3Cbr+%2F%3EClick+%3Ca+href%3Dhttps%3A%2F%2Fmy-api.com%2Fsubscriptions%2Fconfirm%3Ehere+%3C%2Fa%3E+to+confirm+your+scription.&text=Welcome+to+our+newsletter%21%0AVisit+https%3A%2F%2Fmy-api.com%2Fsubscriptions%2Fconfirm+to+confirm+your+scription.&apiUser=mailtest_test_faHRvd&apiKey=mail*api*key"
        let body: BTreeMap<String, String> = serde_qs::from_bytes(&email_request.body).unwrap();
        // 提取链接
        let get_link = |s| {
            let links: Vec<_> = linkify::LinkFinder::new()
                .links(s)
                .filter(|l| *l.kind() == linkify::LinkKind::Url)
                .collect();
            assert_eq!(links.len(), 1);
            links[0].as_str().to_owned()
        };
        let mut html = reqwest::Url::parse(&get_link(&body["html"])).unwrap();
        let mut text = reqwest::Url::parse(&get_link(&body["text"])).unwrap();
        // 设置 URL 中的端口
        html.set_port(Some(self.port)).unwrap();
        text.set_port(Some(self.port)).unwrap();
        ConfirmationLinks { html, text }
    }

    pub async fn get_publish_newsletter(&self) -> reqwest::Response {
        self.api_client
            .get(&format!("{}/admin/newsletters", &self.address))
            .send()
            .await
            .expect("Failed to execute request.")
    }

    pub async fn get_publish_newsletter_html(&self) -> String {
        self.get_publish_newsletter().await.text().await.unwrap()
    }

    pub async fn post_publish_newsletter<Body>(&self, body: &Body) -> reqwest::Response
    where
        Body: serde::Serialize,
    {
        self.api_client
            .post(format!("{}/admin/newsletters", &self.address))
            .form(&body)
            .send()
            .await
            .expect("Failed to execute request.")
    }

    pub async fn post_login<Body>(&self, body: &Body) -> reqwest::Response
    where
        Body: serde::Serialize,
    {
        self.api_client
            .post(format!("{}/login", self.address))
            .form(body) // 该方法确保请求体为 URL 编码，并相应地设置 Content-Type 请求头
            .send()
            .await
            .expect("Failed to execute request.")
    }

    // 我们的测试只需要观察 HTML 页面，因此不需要暴露底层的 reqwest::Response
    pub async fn get_login_html(&self) -> String {
        self.api_client
            .get(format!("{}/login", self.address))
            .send()
            .await
            .expect("Failed to execute request.")
            .text()
            .await
            .unwrap()
    }

    pub async fn get_admin_dashboard(&self) -> reqwest::Response {
        self.api_client
            .get(format!("{}/admin/dashboard", self.address))
            .send()
            .await
            .expect("Failed to execute request.")
    }

    pub async fn get_admin_dashboard_html(&self) -> String {
        self.get_admin_dashboard().await.text().await.unwrap()
    }

    pub async fn get_change_password(&self) -> reqwest::Response {
        self.api_client
            .get(format!("{}/admin/password", self.address))
            .send()
            .await
            .expect("Failed to execute request.")
    }

    pub async fn get_change_password_html(&self) -> String {
        self.get_change_password().await.text().await.unwrap()
    }

    pub async fn post_change_password<Body>(&self, body: &Body) -> reqwest::Response
    where
        Body: serde::Serialize,
    {
        self.api_client
            .post(format!("{}/admin/password", self.address))
            .form(body)
            .send()
            .await
            .expect("Failed to execute request.")
    }

    pub async fn post_logout(&self) -> reqwest::Response {
        self.api_client
            .post(format!("{}/admin/logout", self.address))
            .send()
            .await
            .expect("Failed to execute request.")
    }
}
